package io.gitee.hchaojie.day03.tetris;

import javafx.animation.Animation.Status;
import javafx.animation.FadeTransition;
import javafx.animation.ParallelTransition;
import javafx.animation.PauseTransition;
import javafx.animation.RotateTransition;
import javafx.animation.SequentialTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HorizontalDirection;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;

public class Board extends StackPane implements GameController {
    private static final byte ROWS = 15;
    private static final byte COLS = 15;
    
    // 每次移动的动画时间，时间越长，游戏速度越慢
    private static final double MOVE_DURATION = 0.3;	
    
    private double squareSize;
    private int x = 0, y = 0;	// 砖块初始的列和行
    
    private SequentialTransition moveDownTransition;
    
    /* 左右移动的动画 */
    private TranslateTransition translateTransition;

    /* 旋转动画 */
    private RotateTransition rotateTransition;
    
    private Brick currentBrick; 
    
    private Rectangle[][] squares = new Rectangle[ROWS][COLS];
    
    private ObjectProperty<Brick> nextBrickProperty = new SimpleObjectProperty<>();
    
    public Board(double squareSize) {
    	this.squareSize = squareSize;

    	setMinWidth(COLS * squareSize);
    	setMaxWidth(COLS * squareSize);
    	setMinHeight(ROWS * squareSize);
    	setMaxHeight(ROWS * squareSize);
    	
    	setAlignment(Pos.TOP_LEFT);
    	
    	initMoveDownTransition();
    	
    	setOnKeyPressed(new KeyEventHandler(this));
    }
    
    private void initMoveDownTransition() {
    	TranslateTransition anim = new TranslateTransition();
		anim.setDuration(Duration.seconds(MOVE_DURATION));
		
		PauseTransition pauseAnim = new PauseTransition(Duration.seconds(MOVE_DURATION));
		moveDownTransition = new SequentialTransition(anim, pauseAnim);
		moveDownTransition.setOnFinished(new EventHandler<ActionEvent>() {
			@Override
			public void handle(ActionEvent event) {
				// currentBrick == null表示gameover
				if (currentBrick != null) {
					moveDown();
				}
			}
		});
		
		translateTransition = new TranslateTransition();
		translateTransition.setDuration(Duration.seconds(MOVE_DURATION));
		
		rotateTransition = new RotateTransition();
		rotateTransition.setDuration(Duration.seconds(MOVE_DURATION / 6));
    }
    
    // 使用动画，将砖块向下移动一格
    private void moveDown() {
		if (canMoveDown()) {
			y++;
			getMoveDownTranslateTransition().setToY(y * squareSize);
			moveDownTransition.playFromStart();
		} else {
			boolean isGameOver = placeBrick();
			
			if (!isGameOver) {
				spawnBrick();
			} else {
				nextBrickProperty.set(null);
			}
		}
    }
    
    /**
     * 砖块不能移动后，放置到Board面板上
     * 
     * @return 放置完成之后，是否gameover
     */
    private boolean placeBrick() {
    	int[][] matrix = currentBrick.getMatrix();

		for (int i = 0; i < matrix.length; i++) {
			for (int j = 0; j < matrix[i].length; j++) {
				if (matrix[i][j] == 1) {
					Rectangle square = currentBrick.createRectangle();
					
					square.setTranslateX((x + j) * squareSize);
					square.setTranslateY((y + i) * squareSize);
					
					// 此处要判断gameover
					// gameover逻辑改进：屏幕满了。不能仅仅通过y坐标来判断
					// 1. 如果y坐标 == 0，但是砖块已经到达顶部了，此时要结束游戏
					// 2. 如果y坐标 == -1， 但是
					if (!isValidPosition(y + i, x + j)) {
						// gameover!!!!
						return true;
					} else {
						squares[y + i][x + j] = square;
					}
					
					getChildren().add(square);
				}
			}
		}
		
		getChildren().remove(currentBrick);
		
		// 消除满格子的行
		removePerfectLines(currentBrick);

		currentBrick = null;
		
		return false;
	}
    
    /**
     * 如果一行铺满了格子，先移除该行，然后将上面的行往下移
     */
	private void removePerfectLines(Brick brick) {
		int[][] matrix = brick.getMatrix();
		
		ParallelTransition deleteTransition = new ParallelTransition();
		ParallelTransition moveDownTransition = new ParallelTransition();
		
		int fullRow = 0;
		for (int i = matrix.length - 1; i >= 0; i--) {
			if (y + i < 0 || y + i >= COLS) continue;
			
			boolean isFull = true;
			for (int j = 0; j < COLS; j++) {
				if (this.squares[y + i][j] == null) {
					isFull = false;
					break;
				}
			}
			
			if (isFull) {
				// 当前行满了，删除
				for (int j = 0; j < COLS; j++) {
					Rectangle node = this.squares[y + i][j];
					FadeTransition fade = createFadeTransition(node);
					deleteTransition.getChildren().add(fade);
				}
				
				fullRow++;
			} else if (fullRow > 0) {
				// 当前行往下移动fullRow行
				for (int j = 0; j < COLS; j++) {
					Rectangle node = this.squares[y + i][j];
					if (node != null) {
						TranslateTransition moveDown = createMoveDownTransition(node, fullRow);
						moveDownTransition.getChildren().add(moveDown);
					}
					
					this.squares[y + i + fullRow][j] = node;
				}
			}
		}
		
		if (fullRow > 0) {
			for (int i = y - 1; i >= 0; i--) {
				for (int j = 0; j < COLS; j++) {
					Rectangle node = this.squares[i][j];
					if (node != null) {
						TranslateTransition moveDown = createMoveDownTransition(node, fullRow);
						moveDownTransition.getChildren().add(moveDown);
					}
					
					this.squares[i + 1][j] = node;
				}
			}
		}
		
		SequentialTransition animation = new SequentialTransition();
		animation.getChildren().addAll(deleteTransition, moveDownTransition);
		animation.play();
	}
	
	private TranslateTransition createMoveDownTransition(Node node, int row) {
		TranslateTransition translate = new TranslateTransition();
		translate.setDuration(Duration.millis(50));
		translate.setNode(node);
		translate.setByY(row * squareSize);

		return translate;
	}

	private FadeTransition createFadeTransition(Node node) {
		FadeTransition fade = new FadeTransition(Duration.millis(50));
		fade.setNode(node);
		fade.setAutoReverse(true);
		fade.setCycleCount(3);
		fade.setToValue(0.3);
		fade.setOnFinished((e) -> {
			getChildren().remove(node);
		});
		
		return fade;
	}

	private boolean canMoveDown() {
    	int[][] matrix = currentBrick.getMatrix();
    	
    	// 试探下一行
    	int targetY = y + 1;
    	
    	return !isOutOfBounds(matrix, x, targetY) && !isOverlap(matrix, x, targetY);
    }

	private boolean canMoveHorizentally(int targetX) {
    	int[][] matrix = currentBrick.getMatrix();
    	
    	return !isOutOfBounds(matrix, targetX, y) && !isOverlap(matrix, targetX, y);
    }
	
	// 是否和面板上已有的小格子重合了
	private boolean isOverlap(int[][] matrix, int targetX, int targetY) {
    	// 从最后一行，到第一行，看是否有小方块超出了Board面板
    	for (int i = matrix.length - 1; i >= 0; i--) {
    		for (int j = 0; j < matrix[i].length; j++) {
    			int row = targetY + i;
    			int col = targetX + j;
    			
    			if (!isValidPosition(row, col)) continue;
    	
    			if (matrix[i][j] == 1 && squares[row][col] != null) {
    				return true;
    			}
    		}
    	}
    	
		return false;
	}
	
	private boolean isValidPosition(int x, int y) {
		return 0 <= y && y < ROWS && 0 <= x && x < COLS;
	}
	
    // 从最后一行，到第一行，看是否有小方块超出了Board面板
	private boolean isOutOfBounds(int[][] matrix, int targetX, int targetY) {
    	for (int i = matrix.length - 1; i >= 0; i--) {
    		for (int j = 0; j < matrix[i].length; j++) {
    			boolean isOut = (i + targetY >= ROWS) 
    					|| (j + targetX) < 0
    					|| (j + targetX) >= COLS;
    			if (matrix[i][j] == 1 && isOut) {
    				return true;
    			}
    		}
    	}
    	
    	return false;
	}
    
    private TranslateTransition getMoveDownTranslateTransition() {
    	return (TranslateTransition) moveDownTransition.getChildren().get(0);
    }
    
    public void spawnBrick() {
    	Brick brick = BrickFactory.random(squareSize);
    	if (nextBrickProperty.get() == null) {
    		currentBrick = brick;

    		nextBrickProperty.set(BrickFactory.random(squareSize));
    	} else {
    		currentBrick = nextBrickProperty.get();

    		nextBrickProperty.set(brick);
    	}
    	
    	getChildren().add(currentBrick);
    	
    	// 初始位置
    	x = (COLS - brick.getMatrix().length) / 2;	// 面板中间
    	y = - 2;				// 从屏幕外部进入
    	currentBrick.setTranslateX(x * squareSize);
    	currentBrick.setTranslateY(y * squareSize);

    	moveDownTransition.setNode(currentBrick);
    	moveDownTransition.setRate(1);
    	translateTransition.setNode(currentBrick);
    	rotateTransition.setNode(currentBrick);
		rotateTransition.setToAngle(0);
    	moveDown();
    }
    
    // 清除所有方格
    private void clear() {
    	getChildren().clear();
    	
    	for (int i = 0; i < squares.length; i++) {
    		for (int j = 0; j < squares[i].length; j++) {
    			squares[i][j] = null;
    		}
    	}

    	currentBrick = null;
    }
    
	private BooleanProperty pausedProperty = new SimpleBooleanProperty();

	@Override
	public void start() {
		clear();
		
		spawnBrick();
		
		Platform.runLater(() -> requestFocus()); 
	}

	@Override
	public void pause() {
		if (moveDownTransition.getStatus() == Status.RUNNING) {
			moveDownTransition.pause();
			pausedProperty.set(true);
		}
	}
	
	@Override
	public void speedUp() {
		if (moveDownTransition.getStatus() == Status.RUNNING) {
			moveDownTransition.setRate(20);
		}
	}

	@Override
	public void play() {
		if (moveDownTransition.getStatus() == Status.PAUSED) {
			moveDownTransition.play();
			pausedProperty.set(false);
		}

		Platform.runLater(() -> requestFocus()); 
	}

	@Override
	public void stop() {
		clear();
		pausedProperty.set(false);
	}

	@Override
	public BooleanProperty pausedProperty() {
		return pausedProperty;
	}

	@Override
	public void rotate() {
		if (currentBrick == null) return;
		
		int[][] newMatrix = currentBrick.rotate();
		if (canRotate(newMatrix)) {
			currentBrick.setMatrix(newMatrix);
			
			double angle = rotateTransition.getToAngle();
			rotateTransition.setFromAngle(angle);
			rotateTransition.setToAngle(angle + 90);
			rotateTransition.playFromStart();
		}
	}

	private boolean canRotate(int[][] matrix) {
		return !isOutOfBounds(matrix, x, y) && !isOverlap(matrix, x, y);
	}

	@Override
	public void move(HorizontalDirection direction) {
		if (currentBrick == null) return;
		
		int delta = (direction == HorizontalDirection.LEFT) ? -1 : 1;
		
		if (canMoveHorizentally(x + delta)) {
			x += delta;
			translateTransition.setToX(x * squareSize);
			translateTransition.playFromStart();
		}
	}

	@Override
	public ObjectProperty<Brick> nextBrickProperty() {
		return nextBrickProperty;
	}
}
